package com.it.you.base.bingfa.jikeshijian;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁原则：
 * 1、允许多个线程同时读共享变量；
 * 2、只允许一个线程写共享变量；
 * 3、如果一个写线程正在执行写操作，此时禁止读线程读共享变量。
 * 读写锁与互斥锁的一个重要区别就是读写锁允许多个线程同时读共享变量，而互斥锁是不允许的，这是读写锁在读多写少场景下性能优于互斥锁的关键。
 * 但读写锁的写操作是互斥的，当一个线程在写共享变量的时候，是不允许其他线程执行写操作和读操作。
 *
 * @param <K>
 * @param <V>
 */
public class MyCache<K, V> {
    private final Map<K, V> m = new HashMap<>();
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();
    // 读锁
    private final Lock r = rwl.readLock();
    // 写锁
    private final Lock w = rwl.writeLock();

    // 读缓存
    public V get(K key) {
        r.lock();
        try {
            return m.get(key);
        } finally {
            r.unlock();
        }
    }

    // 写缓存
    public V put(K key, V value) {
        w.lock();
        try {
            return m.put(key, value);
        } finally {
            w.unlock();
        }
    }
}

class MyCache2<K, V> {
    private final Map<K, V> m = new HashMap<>();
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public V get(K key) {
        V v = null;
        //读缓存
        r.lock();    //①
        try {
            v = m.get(key); //②
        } finally {
            r.unlock();     //③
        }
        //缓存中存在，返回
        if (v != null) {   //④
            return v;
        }
        //缓存中不存在，查询数据库
        w.lock();         //⑤
        try {
            //再次验证
            //因为其他线程可能已经查询过数据库，避免重复查询
            v = m.get(key); //⑥
            if (v == null) {  //⑦
                //查询数据库
                //v = "省略代码无数";
                m.put(key, v);
            }
        } finally {
            w.unlock();
        }
        return v;
    }

    // 先是获取读锁，然后再升级为写锁，对此还有个专业的名字，叫锁的升级
    // ReadWriteLock 并不支持这种升级。在上面的代码示例中，读锁还没有释放，此时获取写锁，会导致写锁永久等待，最终导致相关线程都被阻塞
    public V get2(K key) {
        V v = null;
        //读缓存
        r.lock(); //①
        try {
            v = m.get(key); //②
            //缓存中存在，返回
            if (v == null) {
                w.lock();
                try {
                    v = m.get(key);
                    if (v == null) {
                        //查询数据库
                        //v = "省略代码无数";
                        m.put(key, v);
                    }
                } finally {
                    w.unlock();
                }
            } else {
                return v;
            }
        } finally {
            r.unlock(); //③
        }
        return v;
    }
}

//不过，虽然锁的升级是不允许的，但是锁的降级却是允许的。以下代码来源自 ReentrantReadWriteLock 的官方示例，略做了改动。
//你会发现在代码①处，获取读锁的时候线程还是持有写锁的，这种锁的降级是支持的。
class CachedData {
    private Object data;
    private volatile boolean cacheValid;
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();
    //读锁
    private final Lock r = rwl.readLock();
    //写锁
    private final Lock w = rwl.writeLock();

    public void processCachedData() {
        Condition condition = w.newCondition();
        Condition condition1 = r.newCondition();
        // 获取读锁
        r.lock();
        if (!cacheValid) {
            // 释放读锁，因为不允许读锁的升级
            r.unlock();
            // 获取写锁
            w.lock();
            try {
                // 再次检查状态
                if (!cacheValid) {
                    //data = ...
                    cacheValid = true;
                }
                // 释放写锁前，降级为读锁
                // 降级是可以的
                r.lock(); //①
            } finally {
                // 释放写锁
                w.unlock();
            }
        }
        // 此处仍然持有读锁
        try {
            //使用数据
            //use(data);
        } finally {
            r.unlock();
        }
    }
}